﻿using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Reflection;

namespace EIA.SLClient.Utility
{
    public class AncestorBinding
    {
        public string AncestorPath { get; set; }
        public string AncestorTypeName { get; set; }
        public string TargetPath { get; set; }
        public IValueConverter Converter { get; set; }
   

        internal void SetBinding(FrameworkElement element)
        {
            if (string.IsNullOrWhiteSpace(this.AncestorTypeName))
                throw new ArgumentNullException("AncestorTypeName");

            if (string.IsNullOrWhiteSpace(this.TargetPath))
                throw new ArgumentNullException("TargetPath");

            if (this.TryBinding(element) == false)
            {
                element.Loaded += ElementLoaded;
            }
        }

        private void ElementLoaded(object sender, RoutedEventArgs e)
        {
            var element = sender as FrameworkElement;
            element.Loaded -= ElementLoaded;

            this.TryBinding(element);
        }

        private bool TryBinding(FrameworkElement element)
        {
            var dp = element.GetDependencyProperty(this.TargetPath);
            if (dp == null)
                return false;

            var ancestor = element.FindAncestor(this.AncestorTypeName);
            if (ancestor == null)
                return false;

            var binding = new Binding
            {
                Source = ancestor,
                Converter = this.Converter
            };

            if (!string.IsNullOrEmpty(this.AncestorPath))
            {
                binding.Path = new PropertyPath(this.AncestorPath);
            }

            element.SetBinding(dp, binding);
            return true;
        }
    }

    public class BindingHelper
    {
        public static AncestorBinding GetAncestorBinding(DependencyObject target)
        {
            if (target == null)
                throw new ArgumentNullException("target");

            return (AncestorBinding)target.GetValue(AncestorBindingProperty);
        }

        public static void SetAncestorBinding(DependencyObject target, AncestorBinding value)
        {
            if (target == null)
                throw new ArgumentNullException("target");

            target.SetValue(AncestorBindingProperty, value);
        }

        public static readonly DependencyProperty AncestorBindingProperty =
            DependencyProperty.RegisterAttached("AncestorBinding", typeof(AncestorBinding), typeof(FrameworkElement),
            new PropertyMetadata(OnAncestorChanged));

        private static void OnAncestorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ancestorBinding = e.NewValue as AncestorBinding;
            if (ancestorBinding == null)
                throw new ArgumentNullException("AncestorBinding");

            var element = d as FrameworkElement;
            if (element == null)
                throw new ArgumentNullException("target");

            ancestorBinding.SetBinding(element);
        }
    }
    public static class FrameworkElementExtensions
    {
        internal static DependencyProperty GetDependencyProperty(this FrameworkElement element, string propertyName)
        {
            if (element == null || string.IsNullOrEmpty(propertyName))
                return null;

            var dpInfo = element.GetType().GetField(propertyName + "Property", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
            if (dpInfo == null)
                return null;

            return dpInfo.GetValue(element) as DependencyProperty;
        }

        internal static object FindAncestor(this FrameworkElement element, string ancestorTypeName)
        {
            if (string.IsNullOrEmpty(ancestorTypeName))
                return null;

            while (element != null)
            {
                if (IsTypeMatch(element, ancestorTypeName))
                    return element;

                if (IsTypeMatch(element.DataContext, ancestorTypeName))
                    return element.DataContext;

                element = VisualTreeHelper.GetParent(element) as FrameworkElement;
            }

            return null;
        }

        private static bool IsTypeMatch(object obj, string typeName)
        {
            if (obj == null || string.IsNullOrEmpty(typeName))
                return false;

            var type = obj.GetType();
            var interfaces = type.GetInterfaces();
            if (interfaces != null && interfaces.Any(p => p.Name == typeName))
                return true;

            while (type != null)
            {
                if (type.Name == typeName)
                    return true;

                type = type.BaseType;
            }

            return false;
        }
    }
}
